/* http://www.libpng.org/pub/png/book/chapter13.html */

#include "readpng.h"

void readpng_version_info() {
    fprintf(stderr, "  compiled with libpng %s; using libpng %s\n", PNG_LIBPNG_VER_STRING, png_libpng_ver);
    //fprintf(stderr, "  compiled with zlip %s; using zlib %s\n", ZLIB_VERSION, zlib_version);
}

int readpng_init(FILE *infile, long *p_width, long *p_height, int *p_bit_depth, int *p_color_type) {
    /* 1 housekeeping */
    unsigned char sig[8];

    fread(sig, 1, 8, infile);
    if (!png_check_sig(sig, 8))
        return 1; // bad signature

    png_ptr = png_create_read_struct(
        PNG_LIBPNG_VER_STRING, 
        NULL, 
        NULL, 
        NULL
    );
    if (!png_ptr)
        return ERR_MEMALLOC; // out of memory

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, NULL, NULL);
        return ERR_MEMALLOC; // out of memory
    }

    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return ERR_READPNG_INIT;
    }

    /* 2 actual png reading */
    png_init_io(png_ptr, infile);
    png_set_sig_bytes(png_ptr, 8);
    png_read_info(png_ptr, info_ptr);

    png_get_IHDR(
        png_ptr, info_ptr,
        &width, &height,
        p_bit_depth, p_color_type,
        NULL, NULL, NULL
    );

    *p_width = width;
    *p_height = height;

    /* assume no LUT: passes for most PCs */
    static double LUT_exponent = 1.0;
    static double CRT_exponent = 2.2;
    /* set default display exp */
    default_display_exponent = LUT_exponent * CRT_exponent;

    return 0;
}

int readpng_get_bgcolor(unsigned char *red, unsigned char *green, unsigned char *blue) {
    if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD))
        return 1;

    png_color_16p pbackground;
    png_get_bKGD(png_ptr, info_ptr, &pbackground);

    /* half bit_depth requires bitshifts */
    if (bit_depth == 16) {
        *red   = pbackground->red   >> 8;
        *green = pbackground->green >> 8;
        *blue  = pbackground->blue  >> 8;
    /* grayscale is treated specially */
    } else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
        /* bit_depth of 1 gives us only 2 colors */
        if (bit_depth == 1)
            *red = *green = *blue = pbackground->gray ? 255 : 0;
        /* bit_depth of 2 gives us 3 as max value */
        else if (bit_depth == 2)
            *red = *green = *blue = (255/3) * pbackground->gray;
        /* bit_depth of 4 gives us 15 as max value */
        else
            *red = *green = *blue = (255/15) * pbackground->gray;
    } else {
        *red = pbackground->red;
        *green = pbackground->green;
        *blue = pbackground->blue;
    }

    printf("%3d %3d %3d\n", *red, *green, *blue);

    return 0;
}

void readpng_get_image(int *p_channels, unsigned long *p_rowbytes, unsigned char **image_data, png_bytep **row_pointers) {

    if (color_type == PNG_COLOR_TYPE_PALETTE)
        png_set_expand(png_ptr);
    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
        png_set_expand(png_ptr);
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
        png_set_expand(png_ptr);

    if (bit_depth == 16)
        png_set_strip_16(png_ptr);
    if (color_type == PNG_COLOR_TYPE_GRAY ||
        color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
        png_set_gray_to_rgb(png_ptr);

    double gamma;
    if (png_get_gAMA(png_ptr, info_ptr, &gamma))
        png_set_gamma(png_ptr, default_display_exponent, gamma);

    png_uint_32 rowbytes;
    //png_bytep row_pointers[height];
    *row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);

    png_read_update_info(png_ptr, info_ptr);

    *p_rowbytes = rowbytes = png_get_rowbytes(png_ptr, info_ptr);
    *p_channels = (int)png_get_channels(png_ptr, info_ptr);

    if (((*image_data) = (unsigned char*)malloc(rowbytes * height)) == NULL) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        //return NULL;
        return;
    }

    for (int row = 0; row < height; row++)
        (*row_pointers)[row] = (*image_data) + (row * rowbytes);

    /* actual read operation */
    png_read_image(png_ptr, *row_pointers);

    png_read_end(png_ptr, NULL);
}

void readpng_cleanup() {
    if (png_ptr && info_ptr) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        png_ptr  = NULL;
        info_ptr = NULL;
    }
}
